#include "config.h"
#include "asyncio.hh"
#include "logme.hh"
#include "sysexception.hh"
#include <pthread.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <iomanip>
#include <stdexcept>
#if __GLIBC_PREREQ(2,9)
#define HAVE_EPOLL_CREATE1 
#else
#include <fcntl.h>
#endif

#define THROW_NOW(WHAT) \
    do { \
        std::ostringstream __out__; \
        __out__ << WHAT << " {" << __FILE__ << ":" << __LINE__ << " " << __PRETTY_FUNCTION__ << "}"; \
        throw std::runtime_error(__out__.str()); \
    } while (false)

namespace async
{

enum { FAILURE = -1 };

io::io()
#ifdef HAVE_EPOLL_CREATE1
:   epd_(::epoll_create1(EPOLL_CLOEXEC)),
    invoke_add_(normal_add),
    invoke_mod_watch_(normal_mod),
    invoke_mod_(normal_mod),
    invoke_del_(normal_del)
{
    if (epd_ == FAILURE) {
        const int c_errno = errno;
        sys::throw_exception_now< EINVAL, EMFILE, ENFILE, ENOMEM > e;
        THROW_ME_NOW(e, c_errno, "epoll_create1(EPOLL_CLOEXEC) failure: " << std::strerror(c_errno));
    }
    LOG_ME("epoll_create1(EPOLL_CLOEXEC) = " << epd_);
}
#else
:   epd_(::epoll_create(1)),
    invoke_add_(normal_add),
    invoke_mod_watch_(normal_mod),
    invoke_mod_(normal_mod),
    invoke_del_(normal_del)
{
    if (epd_ == FAILURE) {
        const int c_errno = errno;
        sys::throw_exception_now< EINVAL, EMFILE, ENFILE, ENOMEM > e;
        THROW_ME_NOW(e, c_errno, "epoll_create(1) failure: " << std::strerror(c_errno));
    }
    LOG_ME("epoll_create(1) = " << epd_);
    int flags = ::fcntl(epd_, F_GETFD);
    if (flags == FAILURE) {
        const int c_errno = errno;
        sys::throw_exception_now< EACCES, EAGAIN, EBADF, EDEADLK, EFAULT, EINTR,
                                  EINVAL, EMFILE, ENOLCK, EPERM > e;
        THROW_ME_NOW(e, c_errno, "fcntl(" << epd_ << ", F_GETFD) failure: " << std::strerror(c_errno));
    }
    if ((flags & FD_CLOEXEC) == 0) {
        long new_flags = flags | FD_CLOEXEC;
        const int retval = ::fcntl(epd_, F_SETFD, new_flags);
        if (retval == FAILURE) {
            const int c_errno = errno;
            sys::throw_exception_now< EACCES, EAGAIN, EBADF, EDEADLK, EFAULT, EINTR,
                                      EINVAL, EMFILE, ENOLCK, EPERM > e;
            THROW_ME_NOW(e, c_errno, "fcntl(" << epd_ << ", F_SETFD, " << new_flags << ") failure: " << std::strerror(c_errno));
        }
        flags = ::fcntl(epd_, F_GETFD);
        if (flags == FAILURE) {
            const int c_errno = errno;
            sys::throw_exception_now< EACCES, EAGAIN, EBADF, EDEADLK, EFAULT, EINTR,
                                      EINVAL, EMFILE, ENOLCK, EPERM > e;
            THROW_ME_NOW(e, c_errno, "fcntl(" << epd_ << ", F_GETFD) failure: " << std::strerror(c_errno));
        }
        if ((flags & FD_CLOEXEC) == 0) {
            THROW_NOW(flags << " != " << new_flags << " => unable set FD_CLOEXEC flag on epoll descriptor " << epd_);
        }
    }
}
#endif

io::~io()
{
    if (epd_ != FAILURE) {
        LOG_ME("close(" << epd_ << ")");
        ::close(epd_);
        epd_ = FAILURE;
    }
}

namespace
{

enum special_time_value
{
    WAIT_INDEFINITELY  = -1,
    RETURN_IMMEDIATELY =  0,
};

::pthread_mutex_t io_instance_mutex = PTHREAD_MUTEX_INITIALIZER;
io *p_io_instance = NULL;

void lock(::pthread_mutex_t &_mutex)
{
    const int error_code = ::pthread_mutex_lock(&_mutex);
    enum { SUCCESS = 0 };
    if (error_code != SUCCESS) {
        sys::throw_exception_now< EINVAL, EAGAIN, EDEADLK > e;
        THROW_ME_NOW(e, error_code, "pthread_mutex_lock() failure: " << std::strerror(error_code));
    }
}

void unlock(::pthread_mutex_t &_mutex)
{
    const int error_code = ::pthread_mutex_unlock(&_mutex);
    enum { SUCCESS = 0 };
    if (error_code != SUCCESS) {
        sys::throw_exception_now< EINVAL, EAGAIN, EPERM > e;
        THROW_ME_NOW(e, error_code, "pthread_mutex_unlock() failure: " << std::strerror(error_code));
    }
}

} // anonymous namespace

io& io::instance()
{
    if (p_io_instance != NULL) {
        return *p_io_instance;
    }
    lock(io_instance_mutex);
    if (p_io_instance == NULL) {
        static io singleton;
        p_io_instance = &singleton;
    }
    unlock(io_instance_mutex);
    return *p_io_instance;
}

io& io::registry(event_source _fd, event_receiver_fnc _handler, struct events _watch)
{
    return this->add(_fd, _watch, _handler);
}

io& io::change(event_source _fd, struct events _watch)
{
    return this->mod(_fd, _watch);
}

io& io::change(event_source _fd, event_receiver_fnc _handler, struct events _watch)
{
    return this->mod(_fd, _watch, _handler);
}

io& io::free(event_source _fd)
{
    return this->del(_fd);
}

class io::swap_running
{
public:
    swap_running()
    {
        io &obj = io::instance();
        obj.invoke_add_ = io::running_add;
        obj.invoke_mod_watch_ = io::running_mod;
        obj.invoke_mod_ = io::running_mod;
        obj.invoke_del_ = io::running_del;
    }
    ~swap_running()
    {
        io &obj = io::instance();
        obj.invoke_add_ = io::normal_add;
        obj.invoke_mod_watch_ = io::normal_mod;
        obj.invoke_mod_ = io::normal_mod;
        obj.invoke_del_ = io::normal_del;
    }
};

io& io::operator()()
{
    struct ::epoll_event occurred[receiver_.size()];
    enum { FAILURE = -1 };
    const int event_count = ::epoll_wait(epd_, occurred, receiver_.size(), WAIT_INDEFINITELY);
    if (event_count == FAILURE) {
        const int c_errno = errno;
        sys::throw_exception_now< EBADF, EFAULT, EINTR, EINVAL > e;
        THROW_ME_NOW(e, c_errno, "epoll_wait(" << epd_ << ", epoll_event[" << receiver_.size() << ", " << WAIT_INDEFINITELY << ") failure: " << std::strerror(c_errno));
    }
    const struct ::epoll_event *const last_event = occurred + event_count;
    swap_running running_state;
    for (const struct ::epoll_event *event = occurred; event < last_event; ++event) {
        receiver::iterator pReceiver = receiver_.find(event->data.fd);
        if (pReceiver != receiver_.end()) {
            pReceiver->second(event->data.fd, event->events);
        }
    }
    return *this;
}

io& io::add(event_source _fd, struct events _watch, const event_receiver &_handler)
{
    invoke_add_(this, _fd, _watch, _handler);
    return *this;
}

io& io::mod(event_source _fd, struct events _watch)
{
    invoke_mod_watch_(this, _fd, _watch);
    return *this;
}

io& io::mod(event_source _fd, struct events _watch, const event_receiver &_handler)
{
    invoke_mod_(this, _fd, _watch, _handler);
    return *this;
}

io& io::del(event_source _fd)
{
    invoke_del_(this, _fd);
    return *this;
}

void io::normal_add(io *_this, event_source _fd, struct events _watch, const event_receiver &_handler)
{
    if (_this->receiver_.count(_fd) <= 0) {
        _this->receiver_.insert(std::make_pair(_fd, _handler));
    }
    enum { SUCCESS = 0 };
    struct ::epoll_event event;
    event.events = _watch;
    event.data.fd = _fd;
    const int retval = ::epoll_ctl(_this->epd_, EPOLL_CTL_ADD, _fd, &event);
    if (retval == SUCCESS) {
        return;
    }
    const int c_errno = errno;
    sys::throw_exception_now< EBADF, EEXIST, EINVAL, ENOMEM, EPERM > e;
    THROW_ME_NOW(e, c_errno, "epoll_ctl(" << _this->epd_ << ", EPOLL_CTL_ADD, " << _fd << ") failure: " << std::strerror(c_errno));
}

void io::normal_mod(io *_this, event_source _fd, struct events _watch)
{
    enum { SUCCESS = 0 };
    struct ::epoll_event event;
    event.events = _watch;
    event.data.fd = _fd;
    const int retval = ::epoll_ctl(_this->epd_, EPOLL_CTL_MOD, _fd, &event);
    if (retval == SUCCESS) {
        return;
    }
    const int c_errno = errno;
    sys::throw_exception_now< EBADF, EINVAL, ENOENT, ENOMEM, EPERM > e;
    THROW_ME_NOW(e, c_errno, "epoll_ctl(" << _this->epd_ << ", EPOLL_CTL_MOD, " << _fd << ") failure: " << std::strerror(c_errno));
}

void io::normal_mod(io *_this, event_source _fd, struct events _watch, const event_receiver &_handler)
{
    receiver::iterator pReceiver = _this->receiver_.find(_fd);
    if (pReceiver != _this->receiver_.end()) {
        pReceiver->second = _handler;
    }
    normal_mod(_this, _fd, _watch);
}

void io::normal_del(io *_this, event_source _fd)
{
    _this->receiver_.erase(_fd);
    enum { SUCCESS = 0 };
    struct ::epoll_event event;
    const int retval = ::epoll_ctl(_this->epd_, EPOLL_CTL_DEL, _fd, &event);
    if (retval == SUCCESS) {
        return;
    }
    const int c_errno = errno;
    sys::throw_exception_now< EBADF, EINVAL, ENOENT, ENOMEM, EPERM > e;
    THROW_ME_NOW(e, c_errno, "epoll_ctl(" << _this->epd_ << ", EPOLL_CTL_DEL, " << _fd << ") failure: " << std::strerror(c_errno));
}

void io::running_add(io *_this, event_source _fd, struct events _watch, const event_receiver &_handler)
{
    normal_add(_this, _fd, _watch, _handler);
}

void io::running_mod(io *_this, event_source _fd, struct events _watch)
{
    normal_mod(_this, _fd, _watch);
}

void io::running_mod(io *_this, event_source _fd, struct events _watch, const event_receiver &_handler)
{
    normal_mod(_this, _fd, _watch, _handler);
}

void io::running_del(io *_this, event_source _fd)
{
    normal_del(_this, _fd);
}

}
